#nullable disable
namespace Crux.Core.MongoDb;
/// <summary>
/// MongoRepositoryBaseOfT
/// </summary>
public class MongoRepositoryBase<TContext, T, TPrimary>(IMongoUnitOfWork<TContext> mongoUnitOfWork)
        : IMongoRepositoryBase<TContext, T, TPrimary>
        where T : IGenericEntity<TPrimary>
        where TContext : IMongoDBContext
{
    #region private fields
    private readonly IMongoUnitOfWork<TContext> _unitOfWork = mongoUnitOfWork;
    private IMongoCollection<T> _set => Context.DbSet<T, TPrimary>();
    /// <inheritdoc/>
    public TContext Context => _unitOfWork.Context;
    #endregion  private fields

    #region implements IMongoRepositoryBase

    #region ADD
    /// <inheritdoc/>
    public virtual void Add(T entity)
    {
        Requires.NotNull(entity, nameof(entity));

        _set.Add<T, TPrimary>(entity);
    }

    /// <inheritdoc/>
    public async virtual Task AddAsync(
        T entity,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNull(entity, nameof(entity));

        await _set.AddAsync<T, TPrimary>(entity, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public virtual void AddRange(IEnumerable<T> entities)
    {
        Requires.NotNullOrEmpty(entities, nameof(entities));

        _set.AddRange<T, TPrimary>(entities);
    }

    /// <inheritdoc/>
    public async virtual Task AddRangeAsync(
        IEnumerable<T> entities,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNullOrEmpty(entities, nameof(entities));

        await _set.AddRangeAsync<T, TPrimary>(entities, cancellationToken).ConfigureAwait(false);
    }
    #endregion ADD

    #region DELETE
    /// <inheritdoc/>
    public virtual void Delete(T entity)
    {
        Requires.NotNull(entity, nameof(entity));

        _set.Remove<T, TPrimary>(entity);
    }

    /// <inheritdoc/>
    public virtual void Delete(Expression<Func<T, bool>> where)
    {
        Requires.NotNull(where, nameof(where));

        _set.Remove<T, TPrimary>(where);
    }

    /// <inheritdoc/>
    public async virtual Task DeleteAsync(
        T entity,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNull(entity, nameof(entity));

        await _set.RemoveAsync<T, TPrimary>(entity, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async virtual Task DeleteAsync(
        Expression<Func<T, bool>> where,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNull(where, nameof(where));

        await _set.RemoveAsync<T, TPrimary>(where, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public virtual void DeleteRange(IEnumerable<T> entities)
    {
        Requires.NotNullOrEmpty(entities, nameof(entities));

        _set.RemoveRange<T, TPrimary>(entities);
    }

    /// <inheritdoc/>
    public async virtual Task DeleteRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
    {
        Requires.NotNullOrEmpty(entities, nameof(entities));

        await _set.RemoveRangeAsync<T, TPrimary>(entities, cancellationToken).ConfigureAwait(false);
    }
    #endregion DELETE

    #region UPDATE
    /// <inheritdoc/>
    public virtual void Update(T entity)
    {
        Requires.NotNull(entity, nameof(entity));

        _set.Update<T, TPrimary>(entity);
    }

    /// <inheritdoc/>
    public virtual void Update(
        Expression<Func<T, bool>> where,
        Expression<Func<T, T>> expression)
    {
        Requires.NotNull(where, nameof(where));
        Requires.NotNull(expression, nameof(expression));

        var entities = Get(where).ToList();
        static void UpdateInternal(Expression<Func<T, T>> expression, T entity)
        {
            var memberInitExpression = expression.Body as MemberInitExpression;
            if (memberInitExpression is null)
                throw new ArgumentException("The update expression must be of type MemberInitExpression.", "updateExpression");
            foreach (MemberBinding binding in memberInitExpression.Bindings)
            {
                string propertyName = binding.Member.Name;
                var memberAssignment = binding as MemberAssignment;
                if (memberAssignment is null)
                    throw new ArgumentException("The update expression MemberBinding must only by type MemberAssignment.", "updateExpression");
                Expression memberExpression = memberAssignment.Expression;
                object value;
                if (memberExpression.NodeType == ExpressionType.Constant)
                {
                    var constantExpression = memberExpression as ConstantExpression;
                    if (constantExpression is null)
                        throw new ArgumentException(
                            "The MemberAssignment expression is not a ConstantExpression.", "updateExpression");
                    value = constantExpression.Value;
                }
                else
                {
                    LambdaExpression lambda = Expression.Lambda(memberExpression, null);
                    value = lambda.Compile().DynamicInvoke();
                }
                if (value is not null)
                {
                    typeof(T).GetProperty(propertyName).SetValue(entity, value);
                }
            }
        }
        for (int i = 0; i < entities.Count; i++)
        {
            var entity = entities[i];
            var oldId = entity.Id;
            UpdateInternal(expression, entity);
            if (!oldId.Equals(entity.Id))
                throw new Exception();
        }
        UpdateRange(entities);
    }

    /// <inheritdoc/>
    public async virtual Task UpdateAsync(
        T entity,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNull(entity, nameof(entity));

        await _set.UpdateAsync<T, TPrimary>(entity, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async virtual Task UpdateAsync(
        Expression<Func<T, bool>> where,
        Expression<Func<T, T>> expression,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNull(where, nameof(where));
        Requires.NotNull(expression, nameof(expression));

        await Task.Run(() => Update(where, expression), cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public virtual void UpdateRange(IEnumerable<T> entities)
    {
        Requires.NotNullOrEmpty(entities, nameof(entities));

        foreach (var entity in entities)
        {
            Update(entity);
        }
    }

    /// <inheritdoc/>
    public async virtual Task UpdateRangeAsync(
        IEnumerable<T> entities,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNullOrEmpty(entities, nameof(entities));

        await Task.Run(() => UpdateRange(entities), cancellationToken).ConfigureAwait(false);
    }
    #endregion UPDATE

    #region GET

    /// <inheritdoc/>
    public virtual IQueryable<T> Get(
        Expression<Func<T, bool>> where = null,
        CancellationToken cancellationToken = default)
    {
        var query = null == where ? _set.AsQueryable().Where(p => false) : _set.AsQueryable().Where(where);
        cancellationToken.ThrowIfCancellationRequested();
        return query;
    }
    /// <inheritdoc/>
    public async virtual Task<IQueryable<T>> GetAsync(
        Expression<Func<T, bool>> where = null,
        CancellationToken cancellationToken = default)
        => await Task.Run(() => Get(where, cancellationToken), cancellationToken);

    /// <inheritdoc/>
    private IOrderedQueryable<T> GetOrderedQuery(
        IEnumerable<OrderExpression<T>> orderExpressions,
        IQueryable<T> query)
    {
        Requires.NotNullOrEmpty(orderExpressions, nameof(orderExpressions));

        var firstExpression = orderExpressions.FirstOrDefault();
        if (firstExpression.Expression is null)
            throw new ArgumentNullException(nameof(firstExpression.Expression));

        IOrderedQueryable<T> orderQuery = firstExpression.Symbol switch
        {
            OrderSymbol.Asc => query.OrderBy(firstExpression.Expression),
            _ => query.OrderByDescending(firstExpression.Expression)
        };

        foreach (var orderExpression in orderExpressions)
        {
            if (firstExpression == orderExpression) continue;
            if (orderExpression.Expression is null)
                throw new ArgumentNullException(nameof(orderExpression.Expression));

            orderQuery = orderExpression.Symbol switch
            {
                OrderSymbol.Asc => orderQuery.ThenBy(firstExpression.Expression),
                _ => orderQuery.ThenByDescending(firstExpression.Expression)
            };
        }

        return orderQuery;
    }
    /// <inheritdoc/>
    public virtual Pageable<T> GetByPage(
        List<OrderExpression<T>> orderExpressions,
        Expression<Func<T, bool>> where = null,
        PageOption option = default,
        bool desc = false,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNull(orderExpressions, nameof(orderExpressions));

        var query = null == where ? _set.AsQueryable().Where(p => false) : _set.AsQueryable().Where(where);

        query = GetOrderedQuery(orderExpressions, query);

        cancellationToken.ThrowIfCancellationRequested();

        Pageable<T> page = null;
        if (option.PageSize.HasValue && option.PageIndex.HasValue)
        {
            if (0 == option.PageSize)
            {
                page = new Pageable<T>(new List<T>().AsQueryable(), option, 0);
            }
            else
            {
                int totalCount = query.Count();
                cancellationToken.ThrowIfCancellationRequested();
                query = query.Skip((option.PageIndex.Value - 1) * option.PageSize.Value).Take(option.PageSize.Value);
                page = new Pageable<T>(query, option, totalCount);
            }
        }
        else
        {
            int totalCount = query.Count();
            page = new Pageable<T>(query, 1, totalCount, totalCount);
        }

        cancellationToken.ThrowIfCancellationRequested();
        return page;
    }
    /// <inheritdoc/>
    public async virtual Task<Pageable<T>> GetByPageAsync(
        List<OrderExpression<T>> orderExpressions,
        Expression<Func<T, bool>> where = null,
        PageOption option = default,
        bool desc = false,
        CancellationToken cancellationToken = default)
        => await Task.Run(() => GetByPage(orderExpressions, where, option, desc, cancellationToken), cancellationToken);
    #endregion GET
    #endregion implements IMongoRepositoryBase
}